#include "stack.h"
#include "stdlib.h"
#include "mm.h"
#include "error.h"
#include "read.h"
#include "ops.h"
#include <math.h>
#include <string.h>

char * XStringCallers [] = { "quit", "+", "-", "*",  "/", "=","same",
			     "not", "set", "quote", "eval", "car", "cdr",
			     "cons"};

#define USE_SWITCHER int __switcher = 0
#define DECIDE_SW switch(__switcher)
#define IFBOTH case FINUSE_MASK | IINUSE_MASK
#define IFINTEGER case IINUSE_MASK
#define IFFLOAT case FINUSE_MASK
#define ELSE default
#define FINUSE_MASK 0x02
#define IINUSE_MASK 0x01
#define USE_INTEGER __switcher = __switcher | IINUSE_MASK
#define USE_FLOAT   __switcher = __switcher | FINUSE_MASK

static void exit_routine(int i)
{
  printf("Plainsboro-Princton!\n");
  deleteLexical();
  exit(i);
}

int CFOPListLength (void)
{
  /* TODO: May need some love? */
  void * list = STACKPop();
  if (list == MMNil()) return 0;
  if (MMIsType(list, CFCONS_TYPE))
    {
      STACKPush(MMCdr(list));
      return 1 + CFOPListLength();
    }
  else
    ERRORThrow("Object is not a list!");
}

void CFOPQuit (CFCaller * caller)
{
  void * returnValue;
  if (MMCallerLength(caller) > 1)
    ERRORThrow("Quit expects one or none arguments");



  if (MMCallerLength(caller) == 0) {exit_routine (0);}
  returnValue = STACKPop();
  if (MMIsType(returnValue,CFINTEGER_TYPE))
    exit_routine (MMIntegerValue(returnValue));
  else
    ERRORThrow("Quit expects a integer");
}
 
void CFOPAdd (CFCaller * caller)
{
  int i;
  int iResult=0;
  USE_SWITCHER;
  double fResult=0;
  void * op1;
  for (i=0; i<MMCallerLength(caller); i++)
    {
      switch(MMGetType((op1 = STACKPop())))
	{
	case CFINTEGER_TYPE:
	  USE_INTEGER;
	  iResult = iResult + MMIntegerValue(op1);
	  break;
	case CFFLOAT_TYPE:
	  USE_FLOAT;
	  fResult = fResult + MMDoubleValue(op1);
	  break;
	default:
	  ERRORThrow2("Trying to add an object of type ",
		      _TYPE(MMGetType(op1)));
	}
    }
  STACKPop();  

  DECIDE_SW{
  IFBOTH:
    STACKPush(MMDouble(fResult + (double) iResult));
    return;
  IFFLOAT:
    STACKPush(MMDouble(fResult));
    return;
  IFINTEGER:
    STACKPush(MMInteger(iResult));
    return;
  ELSE:
    STACKPush(MMInteger(0));
  }
}

void CFOPSub (CFCaller * caller)
{
  int iResult=0;
  USE_SWITCHER;
  double fResult=0;
  void * total;
  void * op1;
  int i = 0, length = MMCallerLength(caller);
  if (length == 0)
    {
      STACKPop();
      STACKPush(MMInteger(0));
      return;
    }
  
  for (i=0; i<MMCallerLength(caller) - 1; i++)
    {
      switch(MMGetType((op1 = STACKPop())))
	{
	case CFINTEGER_TYPE:
	  USE_INTEGER;
	  iResult = iResult + MMIntegerValue(op1);
	  break;
	case CFFLOAT_TYPE:
	  USE_FLOAT;
	  fResult = fResult + MMDoubleValue(op1);
	  break;
	default:
	  ERRORThrow2("Subtrahend of type ",
		      _TYPE(MMGetType(op1)));
	}
    }

  total = STACKPop();
  STACKPop();  

  if(MMIsType(total, CFINTEGER_TYPE))
    DECIDE_SW{
    IFBOTH:
      STACKPush(MMDouble(MMIntegerValue(total) -
			 (fResult + (double) iResult)));
      return;
    IFFLOAT:
      STACKPush(MMDouble(MMIntegerValue(total) - fResult));
      return;
    IFINTEGER:
      STACKPush(MMInteger(MMIntegerValue(total) - iResult));
      return;
    ELSE:
      STACKPush(MMInteger(0));
    }
  else if(MMIsType(total, CFFLOAT_TYPE))
    STACKPush(MMDouble(MMDoubleValue(total) -
		       (fResult + (double) iResult)));
  else
    ERRORThrow2("Minuend of type ",
		_TYPE(MMGetType(op1)));
}

void CFOPMul (CFCaller * caller)
{
  int i;
  int iResult=1;
  USE_SWITCHER;
  double fResult=1;
  void * op1;
  for (i=0; i<MMCallerLength(caller); i++)
    {
      switch(MMGetType((op1 = STACKPop())))
	{
	case CFINTEGER_TYPE:
	  USE_INTEGER;
	  iResult = iResult * MMIntegerValue(op1);
	  break;
	case CFFLOAT_TYPE:
	  USE_FLOAT;
	  fResult = fResult * MMDoubleValue(op1);
	  break;
	default:
	  ERRORThrow2("Trying to multiply an object of type ",
		      _TYPE(MMGetType(op1)));
	}
    }
  STACKPop();  

  DECIDE_SW{
  IFBOTH:
    STACKPush(MMDouble(fResult * (double) iResult));
    return;
  IFFLOAT:
    STACKPush(MMDouble(fResult));
    return;
  IFINTEGER:
    STACKPush(MMInteger(iResult));
    return;
  ELSE:
    STACKPush(MMInteger(1));
  }
}

void CFOPDiv (CFCaller * caller)
{
  int i;
  int iResult=1;
  double fResult=1;
  void * op1;
  void * op2;
  float total;
  for (i=0; i<MMCallerLength(caller)-1; i++)
    {
      switch(MMGetType((op1 = STACKPop())))
	{
	case CFINTEGER_TYPE:
	  iResult = iResult * MMIntegerValue(op1);
	  break;
	case CFFLOAT_TYPE:
	  fResult = fResult * MMDoubleValue(op1);
	  break;
	default:
	  ERRORThrow2("Divisor of type ",
		      _TYPE(MMGetType(op1)));
	}
    }
  op2 = STACKPop();
  STACKPop();

  if (MMIsType(op2,CFINTEGER_TYPE))
    total = ((double) MMIntegerValue(op2)) /
      (fResult * ((double) iResult));
  else if (MMIsType(op2,CFFLOAT_TYPE))
    total = (MMDoubleValue(op2)) /
      (fResult * (double) iResult);
  else
    ERRORThrow2("Dividend of type ",
		_TYPE(MMGetType(op1)));

  if(((double) ((int) total)) == total)
    STACKPush(MMInteger((int) total));
  else
    STACKPush(MMDouble(total));
    
  
}

#define _EqINTEGER(x,y)	MMIntegerValue(x) == MMIntegerValue(y)
#define _EqDOUBLE(x,y)	MMDoubleValue(x) == MMDoubleValue(y)
#define _EqSTRING(x,y)  !strcmp(MMStringString(x), MMStringString(y))
#define _EqSYMBOL(x,y)				\
  (!strcmp(MMSymbolName(x), MMSymbolName(y)))	\
  && (MMSymbolPackage(op1) == MMSymbolPackage(op2))

#define _EqCHARACTER(x,y)  !MMCharValue(x) == MMCharValue(y)

static int _AUX_CFOPEq (void * op1, void * op2)
{
  int tipo1=MMGetType(op1),
    tipo2=MMGetType(op2);
  if (tipo1 != tipo2)
    return 0;
  else
    switch(tipo1)
      {
      case CFINTEGER_TYPE:
	if(_EqINTEGER(op1,op2))
	  return 1;
	else
	  return 0;
      case CFFLOAT_TYPE:
	if(_EqDOUBLE(op1,op2))
	  return 1;
	else
	  return 0;
      case CFSYMBOL_TYPE:
	if(_EqSYMBOL(op1,op2))
	  return 1;
	else
	  return 0;
      case CFSTRING_TYPE:
	if(_EqSTRING(op1,op2))
	  return 1;
	else
	  return 0;
      case CFCHARACTER_TYPE:
	if(_EqCHARACTER(op1,op2))
	  return 1;
	else
	  return 0;
      case CFCONS_TYPE:
	if(_AUX_CFOPEq (MMCar(op1),
			MMCar(op2)) &&
	   _AUX_CFOPEq (MMCdr(op1),
			MMCdr(op2)))
	  return 1;
	else
	  return 0;	  
      default:
	if(op1 == op2)
	  return 1;
	else
	  return 0;
      }
}

void CFOPEq(CFCaller * caller)
{
  void * op1,* op2;
  int i;
  int length = MMCallerLength(caller);
  int reverseLength = length-1;
  int activo = 1;
  if (length < 2)
    ERRORThrow3("'",_FN(EQUAL), "' expects at least 2 operands");

  op1 = STACKPop();
  
  for (i=0; i<length - 1; i++)
    {
      op2 = STACKPop();
      if (_AUX_CFOPEq(op1,op2))
	reverseLength--;
      else
	{reverseLength--; activo = 0; break;}
    }
  for (i=0; i<reverseLength; i++)
    {
      STACKPop();
    }
  STACKPop();
  if(activo)
    STACKPush(MMT());
  else
    STACKPush(MMNil());
}

void CFOPSame (CFCaller * caller)
{
  void * op1,* op2;
  int i;
  int length = MMCallerLength(caller);
  int reverseLength = length-1;
  int activo=1;
  if (length < 2)
    ERRORThrow3("'",_FN(SAME), "' expects at least 2 operands");

  op1 = STACKPop();
  
  for (i=0; i<length - 1; i++)
    {
      op2 = STACKPop();
      if (op1 == op2)
	reverseLength--;
      else
	{reverseLength--; activo=0; break;}
    }
  for (i=0; i<reverseLength; i++)
    {
      STACKPop();
    }
  STACKPop();
  if(activo)
    STACKPush(MMT());
  else
    STACKPush(MMNil());
}

void CFOPNot (CFCaller * caller)
{
  if(MMCallerLength(caller) != 1)
    ERRORThrow3("'",_FN(SAME), "' expects 1 operand");
  if(STACKPop() == MMNil())
    {STACKPop(); STACKPush(MMT());}
  else
    {STACKPop(); STACKPush(MMNil());}
}
